// 交叉类型（Intersection Types）
function extend<T, U>(first: T, second: U): T & U {
  const result = <T & U>{};
  Object.keys(first).forEach(key=>{
    result[key] = (<any>first)[key];
  })
  Object.keys(second).forEach(key=>{
    // 如果result不存在key 则添加 . 这里避免了second的相同属性覆盖first
    // as any 不是解决了报错,而是绕过了报错检测 , 代码还是存在安全隐患的,所以这里
    // 要求我们“相信我，我知道自己在干什么”
    if(!(result as any).hasOwnProperty(key)){
      result[key] = (<any>second)[key];
    }
  })
  return result;
}
interface A {
  name: string;
}
interface B {
  age: number;
  name: string;
}
const a: A = {name: 'zs'};
const b: B = {age: 20,name: 'ls'};
// 把a和b进行合并形成一个新的交叉类型 同时具有a和b的成员变量.
const ab = extend<A,B>(a,b);
console.log(ab.name); // zs
// 联合类型
// any类型  padding = 30 => 30px  ;  padding='30px' => 30px;
// function padLeft(value: string, padding: any) { }
// 这里采用联合类型,对类型限制为string和number
function padLeft(value: string, padding: string | number) {}
padLeft('left',30);
padLeft('left','30px');
// 如果用any进行padding约束, 那么true是符合ts类型检测的,但是无法参与运算,会造成bug
// 所以对于多个确定的类型可以采用 联合类型进行声明
padLeft('left', true); 
// 联合类型 也可以声明其他类型数据
// const aa: number|string = 123;
// # 类型保护与区分类型
// 联合类型如果是复杂类型,可能会出现问题
// 联合类型也可以包括自定义类型
interface Bird {
  fly: () => any;
  layEggs: () => any;
}

interface Fish {
  swim: () => any;
  layEggs: () => any;
}
// 结果是 Fish也行 Bird也行. 但是二选一就会存在调用的时候出bug
function getSmallPet(): Fish | Bird {
  // ...
}

const pet = getSmallPet();
pet.layEggs(); // okay
if((pet as Fish).swim !== undefined){
  // as 进行类型断言
  (pet as Fish).swim();
  // 在java中如果某个对象是Object,但明确知道是什么类型,可以采用"强制类型转换"
  (<Fish>pet).swim();    // errors
}

// # 可选参数
function f(x: number, y?: number): number {
  return x + (y || 0);
}
// fRs为什么还要声明类型?  为了使以后使用到的时候能明确知道类型
// const fRs: number = f(1,2);
const fRs: number = f(1);
console.log(fRs);

// #语法!
interface UserI{
  name: string;
}
function foo(obj?: UserI){
  // 这里如果没有! 会报错误提示 obj可能为undefined
  // !断言obj肯定使存在的 绕过ts检测
  const {name} = obj!;
  console.log(name);
}
foo();
